using Admin.ApplicationLib;
using Admin.ApplicationLib.Entities;
using Admin.ApplicationLib.Providers;
using Admin.CommonLib.Constants;
using Admin.WebApi.Extensions;
using Admin.WebApi.Infrastructure.Store;
using FluentValidation.AspNetCore;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Minio.AspNetCore;
using Serilog;
using Serilog.Events;
using Swimj.AspNetCore.Infrastructure.Auth;
using Swimj.AspNetCore.Infrastructure.Logging;
using Swimj.AspNetCore.Options;
using Swimj.Core.DependencyInjection;
using Swimj.Core.Providers;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Swimj.CommonLib.Constants;

namespace Admin.WebApi
{
    public static class Program
    {
        public static int Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateBootstrapLogger();

            Log.Information("Starting web host");

            try
            {
                var builder = WebApplication.CreateBuilder(args);

                builder.Host.UseSerilog((context, _, configuration) =>
                    configuration.ReadFrom.Configuration(context.Configuration));

                // Add services to the container.

                builder.Services.AddHttpContextAccessor();

                builder.Services.AddCurrentUser();

                builder.Services.Configure<MinioOptions>(
                    builder.Configuration.GetSection($"Minio"));

                builder.Services.AddMinio();

                // builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
                builder.Services.AddSingleton<IDateTimeProvider, LocalDataTimeProvider>();
                builder.Services.AddScoped<IExcelProvider, EpPlusExcelProvider>();
                builder.Services.AddScoped<IFileProvider, MinioFileProvider>();

                builder.Services.AddDbContext<AdminApplicationDbContext>(options =>
                {
                    var connectionString = builder.Configuration.GetConnectionString("Default");
                    options.UseSqlServer(connectionString, sqlOptions =>
                    {
                        sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName);

                        // Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 

                        sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30),
                            errorNumbersToAdd: null);
                    });
                });

                builder.Services.AddIdentity<UserEntity, RoleEntity>()
                    .AddEntityFrameworkStores<AdminApplicationDbContext>()
                    .AddUserStore<CustomUserStore>()
                    .AddRoleStore<CustomRoleStore>()
                    .AddDefaultTokenProviders();

                builder.Services.Configure<JwtSettings>(
                    builder.Configuration.GetSection($"SecuritySettings:{nameof(JwtSettings)}"));

                builder.Services.AddScoped<JwtSecurityTokenHandler>();

                // 添加身份验证
                builder.Services
                    .AddAuthentication(authentication =>
                    {
                        authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                        authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    })
                    .AddJwtBearer(configureOptions =>
                    {
                        var key = builder.Configuration.GetValue<string>(
                            $"SecuritySettings:{nameof(JwtSettings)}:{nameof(JwtSettings.Key)}");

                        configureOptions.RequireHttpsMetadata = false;
                        configureOptions.SaveToken = true;
                        configureOptions.TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuerSigningKey = true,
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key ?? string.Empty)),
                            ValidateIssuer = false,
                            ValidateAudience = false,
                            RoleClaimType = ClaimTypes.Role,
                            ClockSkew = TimeSpan.Zero
                        };
                    });


                builder.Services.AddAuthorization(options =>
                {
                    // Here I stored necessary permissions/roles in a constant
                    foreach (var prop in typeof(ApiRoutes).GetNestedTypes().SelectMany(c =>
                                 c.GetFields(BindingFlags.Public | BindingFlags.Static |
                                             BindingFlags.FlattenHierarchy)))
                    {
                        options.AddPolicy(prop.Name,
                            policy => policy.RequireClaim(ApplicationClaimTypes.Permission,
                                prop.Name));
                    }
                });

                builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

                //builder.Services.AddScoped<IUserRepository, UserRepository>();
                //builder.Services.AddScoped<IRoleRepository, RoleRepository>();
                //builder.Services.AddScoped<IPermissionRepository, PermissionRepository>();
                //builder.Services.AddScoped<IOrganizationRepository, OrganizationRepository>();
                //builder.Services.AddScoped<IJobRepository, JobRepository>();
                //builder.Services.AddScoped<IUserService, UserService>();
                //builder.Services.AddScoped<IRoleService, RoleService>();
                //builder.Services.AddScoped<IPermissionService, PermissionService>();
                //builder.Services.AddScoped<IOrganizationService, OrganizationService>();
                //builder.Services.AddScoped<IJobService, JobService>();

                builder.Services.AddAssemblies(AppDomain.CurrentDomain.GetAssemblies());

                builder.Services.AddControllers();

                builder.Services.AddHealthChecks()
                    .AddCheck("self", () => HealthCheckResult.Healthy())
                    .AddSqlServer(builder.Configuration.GetConnectionString("Default") ?? string.Empty,
                        name: "AdminDB-check",
                        tags: new[] { "AdminDB" });

                builder.Services.AddCors(options =>
                {
                    options.AddPolicy("CorsPolicy",
                        corsPolicyBuilder => corsPolicyBuilder
                            .SetIsOriginAllowed(_ => true)
                            .AllowAnyMethod()
                            .AllowAnyHeader()
                            .AllowCredentials());
                });

                // builder.Services.AddScoped<IValidator<UserEditRequestDto>, UserEditValidator>();
                builder.Services.AddFluentValidationAutoValidation();
                builder.Services.AddFluentValidationClientsideAdapters();

                builder.Services
                    .AddRequestLogging(builder.Configuration)
                    .AddExceptionMiddleware();

                // builder.Services.Configure<QuartzOptions>(builder.Configuration.GetSection("Quartz"));
                //
                // // if you are using persistent job store, you might want to alter some options
                // builder.Services.Configure<QuartzOptions>(options =>
                // {
                //     options.Scheduling.IgnoreDuplicates = true; // default: false
                //     options.Scheduling.OverWriteExistingData = true; // default: true
                // });
                //
                // builder.Services.AddQuartz(q =>
                // {
                //     // your configuration here
                //
                //     q.UseMicrosoftDependencyInjectionJobFactory();
                //
                //     // q.UseDefaultThreadPool(x => x.MaxConcurrency = 5);
                //     //
                //     // q.UsePersistentStore(x =>
                //     // {
                //     //     x.UseProperties = true;
                //     //     x.UseClustering();
                //     //     // there are other SQL providers supported too 
                //     //     x.UseMySql("server=114.115.164.191;port=3306;Initial Catalog=youAdminDB;user id=root;password=123.com;Charset=utf8");
                //     //     // this requires Quartz.Serialization.Json NuGet package
                //     //     x.UseJsonSerializer();
                //     // });
                // });
                //
                // // Quartz.Extensions.Hosting hosting
                // builder.Services.AddQuartzHostedService(options =>
                // {
                //     // when shutting down we want jobs to complete gracefully
                //     options.WaitForJobsToComplete = true;
                // });

                // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
                builder.Services.AddEndpointsApiExplorer();
                builder.Services.AddSwaggerGen(c =>
                {
                    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                    {
                        Name = "Authorization",
                        In = ParameterLocation.Header,
                        Type = SecuritySchemeType.ApiKey,
                        Scheme = "Bearer",
                        BearerFormat = "JWT",
                        Description =
                            "Input your Bearer token in this format - Bearer {your token here} to access this API",
                    });
                    c.AddSecurityRequirement(new OpenApiSecurityRequirement
                    {
                        {
                            new OpenApiSecurityScheme
                            {
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer",
                                },
                                Scheme = "Bearer",
                                Name = "Bearer",
                                In = ParameterLocation.Header,
                            },
                            new List<string>()
                        },
                    });
                });

                var app = builder.Build();

                // Configure the HTTP request pipeline.
                if (app.Environment.IsDevelopment())
                {
                    app.UseSwagger();
                    app.UseSwaggerUI();
                }

                // app.UseSerilogRequestLogging(options =>
                // {
                //     // Customize the message template
                //     options.MessageTemplate =
                //         "{RequestMethod} {RequestPath} responsed {StatusCode} in {Elapsed} ms, {RequestHost}, {ContentType}";
                //
                //     // Attach additional properties to the request completion event
                //     options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
                //     {
                //         diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
                //         diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
                //     };
                // }); // <-- Add this line

                // app.UseExceptionHandler(exceptionHandlerApp =>
                // {
                //     exceptionHandlerApp.Run(async context =>
                //     {
                //         var response = context.Response;
                //         response.ContentType = "application/json";
                //
                //         var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
                //
                //         if (exceptionHandlerPathFeature != null)
                //         {
                //             switch (exceptionHandlerPathFeature.Error)
                //             {
                //                 case KeyNotFoundException:
                //                     response.StatusCode = (int)HttpStatusCode.NotFound;
                //                     break;
                //
                //                 default:
                //                     response.StatusCode = (int)HttpStatusCode.InternalServerError;
                //                     break;
                //             }
                //
                //             await context.Response.WriteAsync(exceptionHandlerPathFeature.Error.Message);
                //         }
                //     });
                // });

                // app.UseHttpsRedirection();
                app.UseStaticFiles();

                app.UseCors("CorsPolicy");

                app.UseRequestLogging(builder.Configuration)
                    .UseExceptionMiddleware();

                app.UseRouting();

                app.UseAuthentication();

                app.UseCurrentUser();

                app.UseAuthorization();

                app.MapControllers();

                app.MapHealthChecks("/hc", new HealthCheckOptions()
                {
                    Predicate = _ => true,
                    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
                });

                app.MapHealthChecks("/liveness", new HealthCheckOptions
                {
                    Predicate = r => r.Name.Contains("self")
                });

                app.MigrateDbContext<AdminApplicationDbContext>((context, services) =>
                {
                    var logger = services.GetRequiredService<ILogger<AdminApplicationDbContextSeed>>();

                    new AdminApplicationDbContextSeed()
                        .SeedAsync(context, logger)
                        .Wait();
                });

                app.Run();

                return 0;
            }
            catch (Exception e)
            {
                Log.Fatal(e, "");
                return 1;
            }
        }
    }
}